import { Precision } from "../Precision";


/**
* @description 三维坐标
* @author xuld
* @date 2024-11-08
* @lastEditTime 2024-11-08 17:52:26
* @lastEditors xuld
*/
export class XYZ {
    public x: number;
    public y: number;
    public z: number;

    constructor(x: number = 0, y: number = 0, z: number = 0) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    /**
     * @description 验证参数是否为有效数字
     * @throws 当参数无效时抛出错误
     */
    private validateNumbers(...args: number[]): void {
        for (const arg of args) {
            if (typeof arg !== 'number' || isNaN(arg) || !isFinite(arg)) {
                throw new Error('Invalid number parameter');
            }
        }
    }

    /**
     * @description 设置坐标值
     * @param x - X坐标值
     * @param y - Y坐标值
     * @param z - Z坐标值
     * @returns 当前对象
     */
    public set(x: number, y: number, z: number): XYZ {
        this.validateNumbers(x, y, z);
        this.x = x;
        this.y = y;
        this.z = z;
        return this;
    }

    /**
     * @description 计算向量的模长
     * @returns 向量的模长
     */
    public modulus(): number {
        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
    }

    /**
     * @description 计算向量模长的平方
     * @returns 向量模长的平方
     */
    public squareModulus(): number {
        return this.x * this.x + this.y * this.y + this.z * this.z;
    }

    /**
     * @description 归一化向量，返回新向量
     * @param result - 可选的结果对象，用于存储结果
     * @returns 归一化后的新向量
     */
    public normalize(result?: XYZ): XYZ {
        result ||= new XYZ();
        result.set(this.x, this.y, this.z).normalizeSelf();
        return result;
    }

    /**
     * @description 对自身进行归一化
     */
    public normalizeSelf(): void {
        let d = this.modulus();
        if (d < Precision.EPSILON10) {
            this.x = 0;
            this.y = 0;
            this.z = 0;
        }
        else {
            this.x = this.x / d;
            this.y = this.y / d;
            this.z = this.z / d;
        }
    }

    /**
     * @description 判断两个点是否相等
     * @param other - 要比较的点
     * @param tolerance - 容差值，默认为0.0001
     * @returns 是否相等
     */
    public equals(other: XYZ, tolerance: number = Precision.EPSILON4): boolean {
        if (Math.abs(this.x - other.x) > tolerance) return false;
        if (Math.abs(this.y - other.y) > tolerance) return false;
        if (Math.abs(this.z - other.z) > tolerance) return false;
        return true;
    }

    /**
     * @description 向量加法，返回新向量
     * @param other - 要相加的向量
     * @returns 相加后的新向量
     */
    public add(other: XYZ): XYZ {
        return new XYZ(this.x + other.x, this.y + other.y, this.z + other.z);
    }

    /**
     * @description 向量加法，结果存储在自身
     * @param other - 要相加的向量
     */
    public addSelf(other: XYZ): void {
        this.x += other.x;
        this.y += other.y;
        this.z += other.z;
    }

    /**
     * @description 向量减法，返回新向量
     * @param other - 要相减的向量
     * @returns 相减后的新向量
     */
    public subtract(other: XYZ, result?: XYZ): XYZ {
        result ||= new XYZ(this.x, this.y, this.z);
        result.subtractSelf(other);
        return result;
    }

    /**
     * @description 向量减法，结果存储在自身
     * @param other - 要相减的向量
     */
    public subtractSelf(other: XYZ): void {
        this.x -= other.x;
        this.y -= other.y;
        this.z -= other.z;
    }

    /**
     * @description 向量乘以标量，返回新向量
     * @param scalar - 标量值
     * @returns 相乘后的新向量
     */
    public multiply(scalar: number, result?: XYZ): XYZ {
        result ||= new XYZ(this.x, this.y, this.z);
        result.multiplySelf(scalar);
        return result;
    }

    /**
     * @description 向量乘以标量，结果存储在自身
     * @param scalar - 标量值
     */
    public multiplySelf(scalar: number): void {
        this.x *= scalar;
        this.y *= scalar;
        this.z *= scalar;
    }

    /**
     * @description 向量除以标量，返回新向量
     * @param scalar - 标量值
     * @returns 相除后的新向量
     * @throws 当除数为0时抛出错误
     */
    public divide(scalar: number, result?: XYZ): XYZ {
        result ||= new XYZ(this.x, this.y, this.z);
        result.divideSelf(scalar);
        return result;
    }

    /**
     * @description 向量除以标量，结果存储在自身
     * @param scalar - 标量值
     * @throws 当除数为0时抛出错误
     */
    public divideSelf(scalar: number): void {
        if (scalar === 0) {
            throw new Error("Division by zero");
        }
        this.x /= scalar;
        this.y /= scalar;
        this.z /= scalar;
    }

    /**
     * @description 计算两个向量的点积
     * @param other - 另一个向量
     * @returns 点积结果
     */
    public dot(other: XYZ): number {
        return this.x * other.x + this.y * other.y + this.z * other.z;
    }

    /**
     * @description 计算两个向量的叉积，返回新向量
     * @param other - 另一个向量
     * @returns 叉积结果向量
     */
    public cross(other: XYZ, result?: XYZ): XYZ {
        result ||= new XYZ();
        result.x = this.y * other.z - this.z * other.y;
        result.y = this.z * other.x - this.x * other.z;
        result.z = this.x * other.y - this.y * other.x;
        return result;
    }

    /**
     * @description 计算两个向量的叉积，结果存储在自身
     * @param other - 另一个向量
     */
    public crossSelf(other: XYZ): void {
        let xResult = this.y * other.z - this.z * other.y;
        let yResult = this.z * other.x - this.x * other.z;
        this.z = this.x * other.y - this.y * other.x;
        this.x = xResult;
        this.y = yResult;
    }

    /**
     * @description 向量取反，返回新向量
     * @returns 取反后的新向量
     */
    public negate(): XYZ {
        return new XYZ(-this.x, -this.y, -this.z);
    }

    /**
     * @description 向量取反，结果存储在自身
     */
    public negateSelf(): void {
        this.x = -this.x;
        this.y = -this.y;
        this.z = -this.z;
    }

    /**
     * @description 从其他向量复制坐标值
     * @param other - 要复制的向量
     */
    public copyFrom(other: XYZ): void {
        this.x = other.x;
        this.y = other.y;
        this.z = other.z;
    }

    /**
     * @description 创建当前对象的副本
     * @returns 新的 XYZ 对象
     */
    public clone(): XYZ {
        return new XYZ(this.x, this.y, this.z);
    }

    /**
     * @description 转换为字符串表示
     * @returns 格式化的坐标字符串
     */
    public toString(): string {
        return `XYZ(${this.x}, ${this.y}, ${this.z})`;
    }

    /**
     * @description 从数组创建 XYZ 对象
     * @param array - 包含 x,y,z 坐标的数组
     * @param offset - 数组起始偏移量，默认为0
     * @returns 新的 XYZ 对象
     */
    public static fromArray(array: number[], offset: number = 0): XYZ {
        if (!array || array.length < offset + 3) {
            throw new Error("Invalid array length");
        }
        return new XYZ(array[offset], array[offset + 1], array[offset + 2]);
    }

    /**
     * @description 将坐标转换为数组
     * @param target - 目标数组，如果不提供则创建新数组
     * @param offset - 数组起始偏移量，默认为0
     * @returns 包含坐标的数组
     */
    public toArray(target: number[] = [], offset: number = 0): number[] {
        if (offset < 0) {
            throw new Error("Invalid offset");
        }
        target[offset] = this.x;
        target[offset + 1] = this.y;
        target[offset + 2] = this.z;
        return target;
    }

    /**
     * @description 计算两点之间距离
     * @param other - 另一个点
     * @returns 距离
     */
    public distanceTo(other: XYZ): number {
        const dx = this.x - other.x;
        const dy = this.y - other.y;
        const dz = this.z - other.z;
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
    }

    /**
     * @description 判断向量是否为零向量
     * @param tolerance - 容差值，默认为 EPSILON10
     * @returns 是否为零向量
     */
    public isZero(tolerance: number = Precision.EPSILON10): boolean {
        return Math.abs(this.x) < tolerance &&
            Math.abs(this.y) < tolerance &&
            Math.abs(this.z) < tolerance;
    }

    /**
     * @description 线性插值，返回新向量
     * @param other - 另一个点
     * @param t - 插值系数，取值范围为[0,1]
     * @param result - 可选的结果对象，用于存储结果
     * @returns 插值后的新向量
     */
    public lerp(other: XYZ, t: number, result?: XYZ): XYZ {
        result ||= new XYZ();
        result.x = this.x + (other.x - this.x) * t;
        result.y = this.y + (other.y - this.y) * t;
        result.z = this.z + (other.z - this.z) * t;
        return result;
    }

    /**
     * @description 获取向量夹角
     * @param other - 另一个向量
     * @returns 夹角���度
     */
    public angleTo(other: XYZ): number {
        const dot = this.dot(other);
        const mods = this.modulus() * other.modulus();
        if (mods < Precision.EPSILON10) return 0;
        const cos = dot / mods;
        // 处理数值精度导致的问题
        if (cos > 1) return 0;
        if (cos < -1) return Math.PI;
        return Math.acos(cos);
    }

    /**
     * @description 获取与当前向量垂直的任意向量
     * @param result - 可选的结果对象，用于存储结果
     * @returns 垂直向量
     */
    public getPerpendicularVector(result?: XYZ): XYZ {
        result ||= new XYZ();

        if (Math.abs(this.y) < Math.abs(this.x)) {
            result.set(-this.z, 0, this.x);
        } else {
            result.set(0, this.z, -this.y);
        }

        return result.normalize();
    }

    /**
     * @description 计算点到直线的距离
     * @param lineStart - 直线起点
     * @param lineEnd - 直线终点
     * @returns 距离
     */
    public distanceToLine(lineStart: XYZ, lineEnd: XYZ): number {
        const line = lineEnd.subtract(lineStart);
        const v = this.subtract(lineStart);
        const len = line.modulus();
        if (len < Precision.EPSILON10) return v.modulus();
        return v.cross(line).modulus() / len;
    }

    /**
     * @description 向量在另一个向量上的投影
     * @param other - 投影目标向量
     * @param result - 可选的结果对象，用于存储结果
     * @returns 投影向量
     */
    public projectOnto(other: XYZ, result?: XYZ): XYZ {
        result ||= new XYZ();
        const dot = this.dot(other);
        const len2 = other.squareModulus();
        if (len2 < Precision.EPSILON10) {
            result.set(0, 0, 0);
        } else {
            const scale = dot / len2;
            other.multiply(scale, result);
        }
        return result;
    }
}